﻿using Mapster;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using YFurion.Entities;

namespace YFurion.Application
{
    public abstract class MongoDBApplicationService<TEntityDto, TEntity, TGetAllInput> : MongoDBApplicationService<TEntityDto, TEntity, TGetAllInput, TEntityDto, TEntityDto>
       where TEntity : class, IYEntityBase<ObjectId>, new()
       where TEntityDto : class, IEntityDto<ObjectId>
    {
    }

    public abstract class MongoDBApplicationService<TEntityDto, TEntity, TGetAllInput, TCreateInputDto> : MongoDBApplicationService<TEntityDto, TEntity, TGetAllInput, TCreateInputDto, TCreateInputDto>
        where TEntity : class, IYEntityBase<ObjectId>, new()
        where TEntityDto : class, IEntityDto<ObjectId>
        where TCreateInputDto : class, IEntityDto<ObjectId>
    {
    }

    public abstract class MongoDBApplicationService<TEntityDto, TEntity, TGetAllInput, TCreateInputDto, TUpdateInputDto> : MongoDBApplicationServiceBase<TEntity, TEntityDto, TGetAllInput>, IMongoDBApplicationService<TEntityDto, TGetAllInput, TCreateInputDto, TUpdateInputDto>
        where TEntity : class, IYEntityBase<ObjectId>, new()
        where TEntityDto : class, IEntityDto<ObjectId>
        where TCreateInputDto : class, IEntityDto<ObjectId>
        where TUpdateInputDto : class, IEntityDto<ObjectId>
    {
        public virtual async Task<TEntityDto> Get(ObjectId id, CancellationToken cancellationToken = default)
        {
            TEntity entity = await GetEntity(id, cancellationToken);
            return entity.Adapt<TEntityDto>();
        }

        public virtual async Task<PagedResultDto<TEntityDto>> GetAll(TGetAllInput input, CancellationToken cancellationToken = default)
        {
            IMongoQueryable<TEntity> queryable = CreateFilteredQuery(input);

            int totalCount = await queryable.CountAsync(cancellationToken);

            queryable = ApplyPaging(queryable, input);

            List<TEntity> items = await queryable.ToListAsync(cancellationToken);
            PagedResultDto<TEntityDto> result = new PagedResultDto<TEntityDto>
            {
                Items = items.Adapt<List<TEntityDto>>(),
                TotalCount = totalCount,
            };
            return result;
        }

        public virtual async Task<TEntityDto> Post(TCreateInputDto input, CancellationToken cancellationToken = default)
        {
            if (input.Id == ObjectId.Empty)
            {
                input.Id = ObjectId.GenerateNewId();
            }
            TEntity entity = input.Adapt<TEntity>();
            await Collections.InsertOneAsync(entity, cancellationToken: cancellationToken);
            return entity.Adapt<TEntityDto>();
        }

        public virtual async Task<TEntityDto> Put(TUpdateInputDto input, CancellationToken cancellationToken = default)
        {
            TEntity entity = await Collections.Find(e => e.Id.Equals(input.Id)).FirstOrDefaultAsync(cancellationToken);
            await OnUpdateExecuting(entity, cancellationToken);
            TEntityDto entityDto = await UpdateAsync(input, entity, cancellationToken);
            return entityDto;
        }

        public virtual async Task Delete(ObjectId id, CancellationToken cancellationToken = default)
        {
            Expression<Func<TEntity, bool>> predicate = e => e.Id.Equals(id);
            TEntity entity = await Collections.Find(predicate).FirstOrDefaultAsync(cancellationToken);
            await OnDeleteExecuting(entity, cancellationToken);
            await Collections.DeleteOneAsync(predicate, cancellationToken);
        }

        #region 受保护及私有方法
        #region 查询相关
        /// <summary>
        /// 查询一条实体
        /// </summary>
        /// <param name="id"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        protected async Task<TEntity> GetEntity(ObjectId id, CancellationToken cancellationToken = default)
        {
            TEntity entity = await Collections.Find(e => e.Id.Equals(id)).FirstOrDefaultAsync(cancellationToken);
            await OnGetExecuting(entity, cancellationToken);
            return entity;
        }
        #endregion

        #region 修改相关
        /// <summary>
        /// 更新实体
        /// </summary>
        /// <param name="input"></param>
        /// <param name="entity"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        protected async Task<TEntityDto> UpdateAsync(TUpdateInputDto input, TEntity entity, CancellationToken cancellationToken = default)
        {
            entity = input.Adapt(entity);
            return await UpdateAsync(entity, cancellationToken);
        }
        /// <summary>
        /// 更新实体
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        protected async Task<TEntityDto> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
        {
            await Collections.ReplaceOneAsync(e => e.Id.Equals(entity.Id), entity, cancellationToken: cancellationToken);
            return entity.Adapt<TEntityDto>();
        }
        #endregion
        #endregion
    }
}